Skip to content

Make useStore compatible with client-side hydration of SSR components#39

Merged
ai merged 1 commit intonanostores:mainfrom
jmurty:fix-hydration-with-ssr
Mar 14, 2026
Merged

Make useStore compatible with client-side hydration of SSR components#39
ai merged 1 commit intonanostores:mainfrom
jmurty:fix-hydration-with-ssr

Conversation

@jmurty
Copy link
Contributor

@jmurty jmurty commented Mar 12, 2026

The initial value of a store is now returned by useStore on first mount / hydration instead of the store's current value, by providing the Store#init value to the third "getServerSnapshot" argument to useSyncExternalStore.

This change avoids hydration errors on projects that use Server Side Rendering (SSR) combined with client-side navigation and rendering, where the client-side store value can get out of sync with the value in the server-rendered page especially if the server page is cached in any way (ISR).

This requires an upcoming release of nanostores including the new Store#init attribute from PR:
nanostores/nanostores#390

Fixes #38

The initial value of a store is now returned by `useStore` on first
mount / hydration instead of the store's current value, by providing
the `Store#init` value to the third "getServerSnapshot" argument to
`useSyncExternalStore`.

This change avoids hydration errors on projects that use Server Side
Rendering (SSR) combined with client-side navigation and rendering,
where the client-side store value can get out of sync with the value
in the server-rendered page especially if the server page is cached
in any way (ISR).

This requires an upcoming release of nanostores including the new
`Store#init` attribute from PR:
nanostores/nanostores#390

Fixes nanostores#38
@jmurty
Copy link
Contributor Author

jmurty commented Mar 12, 2026

NOTE: Tests are expected to fail for this PR until Store#init is available.

Also note that this code change was straight-forward, but finding a way to simulate the SSR + hydration behaviour in the tests was a bit of a nightmare. I'm not sure I've got the tests completely right, but they do at least fail due to hydration errors for nanostores 1.1.1 then pass when run against a merge of nanostores/nanostores#390

: store.listen(emit(snapshotRef, onChange))
}, deps)
let get = () => snapshotRef.current
// `'init' in store` check for compatibility with nanostores <= 1.1.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can add ^1.2.0 to peer dependencies and avoid this check

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Would you like a PR to remove this check? I have the commit ready but you beat me to the version bump

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already fixed it and release :)

@ai ai merged commit 183d0f1 into nanostores:main Mar 14, 2026
0 of 3 checks passed
@ai
Copy link
Member

ai commented Mar 14, 2026

Released in 1.0.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect usage of useSyncExternalStore results in Hydration errors with SSR.

2 participants